package com.cr.dataprotect.manage;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.cr.dataprotect.bean.SensitiveConfigBean;
import com.cr.dataprotect.common.MyConstants;
import com.cr.dataprotect.provider.AbstractProvider;
import com.cr.dataprotect.sdk.CryptoApi;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据加解密管理类，继承自 AbstractProvider，负责对数据库操作中的敏感参数进行加密，
 * 以及对查询结果中的加密数据进行解密。
 *
 * @author ChenRu1
 * @since 2023/12/5
 */
@Slf4j
@Service
public class SensitiveManage extends AbstractProvider {
    @Autowired
    private CryptoApi cryptoApi;

    /**
     * 添加/编辑时对敏感参数进行加密处理
     *
     * @param invocation  MyBatis 插件的调用信息
     * @param fieldConfig 敏感字段配置信息，键为类类型，值为包含敏感字段和密钥信息的映射
     */
    public void sensitiveParameter(Invocation invocation, Map<Class, Map<String, Object>> fieldConfig) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) PluginUtils.realTarget(invocation.getTarget());
        Object parameterObject = parameterHandler.getParameterObject();
        MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");

        MapperMethod.ParamMap<Object> paramMap = getParamMap(parameterObject);

        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            String paramName = entry.getKey();
            Object paramValue = entry.getValue();
            if (paramValue == null) {
                continue;
            }
            Class<?> valueClass = paramValue.getClass();
            // 跳过没有敏感字段配置的类
            if (!fieldConfig.containsKey(valueClass)) {
                continue;
            }

            BoundSql boundSql = getBoundSql(parameterHandler);
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            // 跳过参数映射列表为空的情况
            if (CollectionUtils.isEmpty(parameterMappings)) {
                continue;
            }
            // 获取敏感字段配置列表
            List<SensitiveConfigBean> sensitiveConfigBeanList = (List<SensitiveConfigBean>) fieldConfig.get(valueClass).get(MyConstants.PROPERTIES);
            // 获取敏感字段名称列表
            List<String> sensitiveFields = sensitiveConfigBeanList.stream().map(SensitiveConfigBean::getName).collect(Collectors.toList());

            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() == ParameterMode.OUT) {
                    continue;
                }
                String propertyName = parameterMapping.getProperty();
                String trimmedParamName = getTrimmedParamName(paramName, propertyName);

                if (sensitiveFields.contains(trimmedParamName)) {
                    Object value = getParameterValue(boundSql, mappedStatement, parameterObject, paramValue, propertyName, trimmedParamName);
                    if (value != null) {
                        String valueStr = String.valueOf(value);
                        if (StringUtils.isNotBlank(valueStr) && !valueStr.startsWith(MyConstants.CRYPT_PREFIX)) {
                            // 获取加密密钥
                            String secretKey = getSecretKey(fieldConfig, valueClass,MyConstants.SENSITIVE);
                            if (ObjectUtils.isNotEmpty(secretKey)) {
                                // 执行加密操作
                                value = MyConstants.CRYPT_PREFIX + cryptoApi.encrypt(valueStr, secretKey);
                                // 设置加密后的值
                                setFieldValue(paramValue, trimmedParamName, value);
                                log.debug("SM4 encode,table={},field={},encodeStr={}", paramValue, trimmedParamName, value);
                            } else {
                                log.error(SECRETKEY_ERROR_MSG);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 将参数对象转换为 MapperMethod.ParamMap 对象
     *
     * @param parameterObject 参数对象
     * @return MapperMethod.ParamMap 对象
     */
    private MapperMethod.ParamMap<Object> getParamMap(Object parameterObject) {
        if (parameterObject instanceof MapperMethod.ParamMap) {
            return (MapperMethod.ParamMap<Object>) parameterObject;
        } else {
            MapperMethod.ParamMap<Object> paramMap = new MapperMethod.ParamMap<>();
            paramMap.put("", parameterObject);
            return paramMap;
        }
    }

    /**
     * 通过反射获取 ParameterHandler 中的 BoundSql 对象
     *
     * @param parameterHandler ParameterHandler 对象
     * @return BoundSql 对象
     */
    private BoundSql getBoundSql(ParameterHandler parameterHandler) throws NoSuchFieldException, IllegalAccessException {
        Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");
        boundSqlField.setAccessible(true);
        return (BoundSql) boundSqlField.get(parameterHandler);
    }

    /**
     * 获取修剪后的参数名
     *
     * @param paramName    参数名
     * @param propertyName 属性名
     * @return 修剪后的参数名
     */
    private String getTrimmedParamName(String paramName, String propertyName) {
        String trimmedParamName = propertyName;
        if (StringUtils.isNotEmpty(paramName) && trimmedParamName.startsWith(paramName + ".")) {
            trimmedParamName = trimmedParamName.substring(paramName.length() + 1);
            log.debug("origin param name: {}, trimmed param name:{}", propertyName, trimmedParamName);
        }
        return trimmedParamName;
    }

    /**
     * 获取参数的值
     *
     * @param boundSql         BoundSql 对象
     * @param mappedStatement  映射语句对象
     * @param parameterObject  参数对象
     * @param paramValue       参数值
     * @param propertyName     属性名
     * @param trimmedParamName 修剪后的参数名
     * @return 参数的值
     */
    private Object getParameterValue(BoundSql boundSql, MappedStatement mappedStatement, Object parameterObject, Object paramValue, String propertyName, String trimmedParamName) throws IllegalAccessException, NoSuchFieldException {
        Object value;
        if (boundSql.hasAdditionalParameter(propertyName)) {
            value = boundSql.getAdditionalParameter(propertyName);
        } else if (mappedStatement.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
            value = paramValue;
        } else {
            Configuration configuration = mappedStatement.getConfiguration();
            MetaObject newMetaObject = configuration.newMetaObject(paramValue);
            value = newMetaObject.getValue(trimmedParamName);
            Field additionalParametersField = boundSql.getClass().getDeclaredField("additionalParameters");
            additionalParametersField.setAccessible(true);
            Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
            if (value == null && MapUtils.isNotEmpty(additionalParameters)) {
                value = additionalParameters.get(propertyName);
            }
        }
        return value;
    }

    /**
     * 通过反射设置对象的字段值
     *
     * @param paramValue 对象实例
     * @param fieldName  字段名
     * @param value      要设置的值
     */
    private void setFieldValue(Object paramValue, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = paramValue.getClass().getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        declaredField.set(paramValue, value);
    }

    /**
     * 对加密后的值进行解密处理
     *
     * @param result      待解密的结果对象
     * @param fieldConfig 敏感字段配置信息，键为类类型，值为包含敏感字段和密钥信息的映射
     * @return 解密后的结果对象
     */
    public Object decrypt(Object result, Map<Class, Map<String, Object>> fieldConfig) throws Throwable {
        if (result instanceof Collection) {
            Collection<?> resultList = (Collection<?>) result;
            if (CollectionUtils.isNotEmpty(resultList)) {
                for (Object o : resultList) {
                    if (o != null && fieldConfig.containsKey(o.getClass())) {
                        String secretKey = getSecretKey(fieldConfig, o.getClass(),MyConstants.SENSITIVE);
                        if (StrUtil.isNotEmpty(secretKey)) {
                            process(o, secretKey, (List<SensitiveConfigBean>) fieldConfig.get(o.getClass()).get(MyConstants.PROPERTIES));
                        } else {
                            log.error(SECRETKEY_ERROR_MSG);
                        }
                    }
                }
            }
        } else if (result != null && fieldConfig.containsKey(result.getClass())) {
            String secretKey = getSecretKey(fieldConfig, result.getClass(),MyConstants.SENSITIVE);
            if (ObjectUtils.isNotEmpty(secretKey)) {
                process(result, secretKey, (List<SensitiveConfigBean>) fieldConfig.get(result.getClass()).get(MyConstants.PROPERTIES));
            } else {
                log.error(SECRETKEY_ERROR_MSG);
            }
        }
        return result;
    }

    /**
     * 对返回值对象的具体处理过程.
     *
     * @param resultObj              返回值对象
     * @param secretKey              加密秘钥
     * @param needEncryptPropertyArr 需要加密的属性列表
     * @throws IllegalAccessException 反射可能抛出的异常
     */
    private void process(Object resultObj, String secretKey, List<SensitiveConfigBean> needEncryptPropertyArr) throws IllegalAccessException {
        if (resultObj == null) {
            return;
        }
        decryptObject(resultObj, secretKey, MyConstants.CRYPT_PREFIX, needEncryptPropertyArr);
    }




    /**
     * 对对象的所有敏感字段进行解密
     *
     * @param resultObj           待解密的对象
     * @param secretKey           加密密钥
     * @param cryptPrefix         加密前缀
     * @param needEncryptProperty 需要加密的属性列表
     */
    public void decryptObject(Object resultObj, String secretKey,
                              String cryptPrefix, List<SensitiveConfigBean> needEncryptProperty) throws IllegalAccessException {
        //保存已处理过的对象，避免循环处理
        Set<Object> handlingClasses = Sets.newHashSet();
        decryptAllFields(resultObj, secretKey, cryptPrefix, handlingClasses, needEncryptProperty);
    }

    /**
     * 递归解密对象的所有字段
     *
     * @param resultObj           待解密的对象
     * @param secretKey           加密密钥
     * @param cryptPrefix         加密前缀
     * @param handlingClasses     已处理的对象集合
     * @param needEncryptProperty 需要加密的属性列表
     */
    private void decryptAllFields(Object resultObj, String secretKey,
                                  String cryptPrefix, Set<Object> handlingClasses, List<SensitiveConfigBean> needEncryptProperty)
            throws IllegalAccessException {
        List<Field> declaredFields = new ArrayList<>();
        //直接反射取得field有stack overflow问题
        ReflectionUtils.doWithFields(resultObj.getClass(), declaredFields::add);

        int cryptPrefixLength = cryptPrefix.length();
        List<String> needEncryptPropertyNames = needEncryptProperty.stream().map(SensitiveConfigBean::getName).collect(Collectors.toList());

        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            Object value = declaredField.get(resultObj);
            if (value == null) {
                continue;
            }

            if (value instanceof String) {
                // 处理字符串类型的字段
                decryptField(resultObj, secretKey, cryptPrefix, cryptPrefixLength, declaredField, (String) value, needEncryptPropertyNames);
            } else if (value instanceof Character) {
                // 处理字符类型的字段
                decryptField(resultObj, secretKey, cryptPrefix, cryptPrefixLength, declaredField, ((Character) value).toString(), needEncryptPropertyNames);
            } else if (value.getClass().isArray()) {
                // 处理数组类型的字段
                handleArray(resultObj, secretKey, cryptPrefix, handlingClasses, needEncryptProperty, value, declaredField, cryptPrefixLength);
            } else if (value instanceof Collection) {
                // 处理集合类型的字段
                handleCollection(resultObj, secretKey, cryptPrefix, handlingClasses, needEncryptProperty, (Collection<?>) value);
            } else if (canDecryptedType(value)) {
                if (handlingClasses.contains(resultObj)) {
                    return;
                }
                handlingClasses.add(resultObj);
                decryptAllFields(value, secretKey, cryptPrefix, handlingClasses, needEncryptProperty);
            }
        }
    }

    /**
     * 处理数组类型的字段
     *
     * @param resultObj           待解密的对象
     * @param secretKey           加密密钥
     * @param cryptPrefix         加密前缀
     * @param handlingClasses     已处理的对象集合
     * @param needEncryptProperty 需要加密的属性列表
     * @param value               数组对象
     * @param declaredField       字段对象
     * @param cryptPrefixLength   加密前缀长度
     */
    private void handleArray(Object resultObj, String secretKey, String cryptPrefix, Set<Object> handlingClasses, List<SensitiveConfigBean> needEncryptProperty, Object value, Field declaredField, int cryptPrefixLength) throws IllegalAccessException {
        int length = Array.getLength(value);
        if (length > 0) {
            if (value.getClass().getComponentType() == char.class) {
                String valueToStr = new String((char[]) value);
                List<String> needEncryptPropertyNames = needEncryptProperty.stream().map(SensitiveConfigBean::getName).collect(Collectors.toList());
                decryptField(resultObj, secretKey, cryptPrefix, cryptPrefixLength, declaredField, valueToStr, needEncryptPropertyNames);
            } else {
                for (int idx = 0; idx < length; idx++) {
                    Object object = Array.get(value, idx);
                    if (canDecryptedType(object)) {
                        decryptAllFields(object, secretKey, cryptPrefix, handlingClasses, needEncryptProperty);
                    }
                }
            }
        }
    }

    /**
     * 处理集合类型的字段
     *
     * @param resultObj           待解密的对象
     * @param secretKey           加密密钥
     * @param cryptPrefix         加密前缀
     * @param handlingClasses     已处理的对象集合
     * @param needEncryptProperty 需要加密的属性列表
     * @param value               集合对象
     */
    private void handleCollection(Object resultObj, String secretKey, String cryptPrefix, Set<Object> handlingClasses, List<SensitiveConfigBean> needEncryptProperty, Collection<?> value) throws IllegalAccessException {
        Iterator<?> iterator = value.iterator();
        while (iterator.hasNext()) {
            decryptAllFields(iterator.next(), secretKey, cryptPrefix, handlingClasses, needEncryptProperty);
        }
    }

    /**
     * 判断对象是否可以进行解密处理
     *
     * @param value 待判断的对象
     * @return 如果可以解密返回 true，否则返回 false
     */
    private static boolean canDecryptedType(Object value) {
        return value != null && !(value.getClass().isPrimitive() || value.getClass().isEnum() || value instanceof Number
                || value instanceof Boolean || value instanceof Date || value instanceof Time || value instanceof Timestamp);
    }

    /**
     * 对单个字段进行解密处理
     *
     * @param resultObj                待解密的对象
     * @param secretKey                加密密钥
     * @param cryptPrefix              加密前缀
     * @param cryptPrefixLength        加密前缀长度
     * @param declaredField            字段对象
     * @param value                    字段值
     * @param needEncryptPropertyNames 需要加密的属性名列表
     */
    private void decryptField(Object resultObj, String secretKey, String cryptPrefix,
                              int cryptPrefixLength, Field declaredField, String value,
                              List<String> needEncryptPropertyNames) throws IllegalAccessException {
        if (!needEncryptPropertyNames.contains(declaredField.getName())) {
            return;
        }
        if (!value.startsWith(cryptPrefix)) {
            return;
        }
        String encryptedValue = value.substring(cryptPrefixLength);
        try {
            String decrypted = cryptoApi.decrypt(encryptedValue, secretKey);
            if (declaredField.getType() == char[].class) {
                declaredField.set(resultObj, decrypted.toCharArray());
            } else {
                declaredField.set(resultObj, decrypted);
            }
        } catch (Exception e) {
            log.error("解密失败，字段: {}, 值: {}", declaredField.getName(), value, e);
        }
    }
}